Louisville Weather Time Series Analysis

This is the seconds in a series of files regarding the analysis of Weather Data in Louisville.

For this analysis I will be using a few packages which you will need loaded in order to follow along:

# List of packages to load:
packages <- c("tidyverse", "lubridate", "tibbletime", "rlang", "dygraphs", "forecast", "zoo", "xts", "stringr")
  
# Check to see whether any packages aren't installed on the computer and install
new_packages <- packages[!(packages %in% installed.packages()[,"Package"])]
if(length(new_packages)) install.packages(new_packages)
  
# Load Neccessary Packages
sapply(packages, require, character.only = TRUE)
rm(new_packages)

Data Import

Data was imported, cleaned, and regularized in Weather_01. In order to import the data now we just need to load the RData file.

load(file = "BowmanField_Weather.RData")

Visualize Data

Plot Data and discuss

Temperature

temp.xts <- xts(select(filter_time(bf.tt, '2012' ~ '2017'),TEMP),order.by = filter_time(bf.tt, '2012' ~ '2017')$DATE_TIME)
temp.xts %>% dygraph(main="Bowman Field Temperature (F)") %>%
  dyAxis('y', label = "Temperature (F)") %>%
  dySeries("TEMP", axis = 'y') %>%
  dyRangeSelector(dateWindow = c("2016-01-01","2017-12-31")) %>%
  dyUnzoom()

A review of the graph shows a couple of things of interest. The first interesting thing is that there is a wide variation in daily temperatures, but it appears that there may be more variation between days during the winter than during the summer. It might be better to use daily Max/Min/Mean data for this analysis than to use hourly data.

Actions to be taken:

  1. Aggregate to daily
  1. Column for each of Max, Min, Mean

Wind Direction

dir.xts <- xts(select(filter_time(bf.tt, '2012' ~ '2017'),DIR),order.by = filter_time(bf.tt, '2012' ~ '2017')$DATE_TIME)
dir.xts %>% dygraph(main="Bowman Field Wind Direction (Compass Degrees)") %>%
  dyAxis('y', label = "Wind Direction (Compass Degrees)") %>%
  dySeries("DIR", axis = 'y') %>%
  dyRangeSelector(dateWindow = c("2016-01-01","2017-12-31")) %>%
  dyUnzoom()

From the

Wind Speed

spd.xts <- xts(select(filter_time(bf.tt, '2012' ~ '2017'),SPD),order.by = filter_time(bf.tt, '2012' ~ '2017')$DATE_TIME)
spd.xts %>% dygraph(main="Bowman Field Wind Speed (mph)") %>%
  dyAxis('y', label = "Wind speed (mph)") %>%
  dySeries("SPD", axis = 'y') %>%
  dyRangeSelector(dateWindow = c("2016-01-01","2017-12-31")) %>%
  dyUnzoom()

Wind speed appears to be seasonal, with maximum wind speed expected in the Spring.

Some actions to take:

  1. Convert data to daily
  1. Column for Max, Min, and Mean wind speed per day

Precipitation

precip.xts <- xts(select(filter_time(bf.tt, '2012' ~ '2017'),PCP01),order.by = filter_time(bf.tt, '2012' ~ '2017')$DATE_TIME)
precip.xts %>% dygraph(main="Bowman Field Precipitation (Inches)") %>%
  dyAxis('y', label = "Precipitation (In)") %>%
  dySeries("PCP01", axis = 'y') %>%
  dyRangeSelector(dateWindow = c("2016-01-01","2017-12-31")) %>%
  dyUnzoom()

There are a couple of interesting things to think about on this graph. First, there appears to be some sort of seasonality in the amplitude of the precipiation amounts. More rain per hour is expected during the summer when it rains. Second, this graph is only showing amplitude of the data, but is not clear how often it rains. Time since last rain may be as important to air quality as the amount of rain is.

Actions to take:

  1. Aggregate data to daily by taking Sum of rain in that day.
  2. Create a new column for days since last rain (also daily).

Aggregate data to daily

Next step is to aggregate all of the data into daily values.

Start with a function to make it clearer and easier.

tt_period_apply <- function(in.tbl_time, in.period, in.func = mean, na_rm = TRUE) {
  # Programatically find the index column
  index_column <- attributes(in.tbl_time)$index_quo[[2]]
  # Collapse by period, group by index, then summarise by function.
  out.tbl_time <- in.tbl_time %>%
    collapse_by(in.period) %>%
    group_by(!!! sym(index_column)) %>%
    summarise_if(is.numeric, in.func, na.rm = na_rm)
}

Aggregate Temperature

temporary.tt <- bf.tt %>% select(DATE_TIME,TEMP)
bf.daily.tt <- tt_period_apply(temporary.tt, 'daily', in.func = mean, na_rm = TRUE) %>%
  select(DATE_TIME, mean.temp = TEMP)
bf.daily.tt <- tt_period_apply(temporary.tt, 'daily', in.func = max, na_rm = TRUE) %>%
  select(DATE_TIME, max.temp = TEMP) %>%
  full_join(bf.daily.tt,by = 'DATE_TIME')
bf.daily.tt <- tt_period_apply(temporary.tt, 'daily', in.func = min, na_rm = TRUE) %>%
  select(DATE_TIME, min.temp = TEMP) %>%
  full_join(bf.daily.tt,by = 'DATE_TIME')
rm(temporary.tt)

Convert Wind Direction to N/S and E/W values

temporary.tt <- bf.tt %>% select(DATE_TIME, DIR)
# Convert directions in degrees to Radians, then to x and y
temporary.tt <- temporary.tt %>%
  mutate(dir.x = round(sin(DIR*pi/180),digits = 6),
         dir.y = round(cos(DIR*pi/180),digits = 6))

Aggregate Wind Direction X and Y

temporary.tt <- temporary.tt %>% select(DATE_TIME,dir.x, dir.y)
bf.daily.tt <- tt_period_apply(temporary.tt, 'daily', in.func = mean, na_rm = TRUE) %>%
  select(DATE_TIME, mean.dir.x = dir.x, mean.dir.y = dir.y) %>%
  full_join(bf.daily.tt, by = 'DATE_TIME')
bf.daily.tt <- tt_period_apply(temporary.tt, 'daily', in.func = min, na_rm = TRUE) %>%
  select(DATE_TIME, min.dir.x = dir.x, min.dir.y = dir.y) %>%
  full_join(bf.daily.tt, by = 'DATE_TIME')
bf.daily.tt <- tt_period_apply(temporary.tt, 'daily', in.func = max, na_rm = TRUE) %>%
  select(DATE_TIME, max.dir.x = dir.x, max.dir.y = dir.y) %>%
  full_join(bf.daily.tt, by = 'DATE_TIME')
rm(temporary.tt)

Aggregate Wind Speed

temporary.tt <- bf.tt %>% select(DATE_TIME,SPD)
bf.daily.tt <- tt_period_apply(temporary.tt, 'daily', in.func = mean, na_rm = TRUE) %>%
  select(DATE_TIME, mean.spd = SPD) %>%
  full_join(bf.daily.tt,by = 'DATE_TIME')
bf.daily.tt <- tt_period_apply(temporary.tt, 'daily', in.func = max, na_rm = TRUE) %>%
  select(DATE_TIME, max.spd = SPD) %>%
  full_join(bf.daily.tt,by = 'DATE_TIME')
bf.daily.tt <- tt_period_apply(temporary.tt, 'daily', in.func = min, na_rm = TRUE) %>%
  select(DATE_TIME, min.spd = SPD) %>%
  full_join(bf.daily.tt,by = 'DATE_TIME')
rm(temporary.tt)

Aggregate Precipitation

temporary.tt <- bf.tt %>% select(DATE_TIME, PCP01)
bf.daily.tt <- tt_period_apply(temporary.tt, 'daily', in.func = sum, na_rm = TRUE) %>%
  select(DATE_TIME, sum.precip = PCP01) %>%
  full_join(bf.daily.tt,by = 'DATE_TIME')
rm(temporary.tt)

Add new column for days since last Precipitation

bf.daily.tt <- bf.daily.tt %>% 
  mutate(days.since.precip = NA)
rain_data_detected <- FALSE
for(row in 1:length(bf.daily.tt$days.since.precip)) {
  if(rain_data_detected) {
    bf.daily.tt$days.since.precip[row] <- ifelse(bf.daily.tt$sum.precip[row] <= 0.01, 
                                        ifelse(is.na(bf.daily.tt$days.since.precip[row-1]),
                                               1,
                                               bf.daily.tt$days.since.precip[row-1] + 1), 
                                        0)
  } else {
    if(bf.daily.tt$sum.precip[row] >= 0.01) {
      rain_data_detected <- TRUE
      bf.daily.tt$days.since.precip[row] <- 0
    }
  }
  
}
rm(rain_data_detected,row)

Determine what to do about NA values.

For most of the time series analysis the program needs to know how to handle NA values. Not an easy thing to do in some cases. Omitting NA values creates a problem because the functions assume that a seasonal year is 365 days (integer closest to 365.25), but with NA values missing that will be wrong.

tt_na_stats <- function(in.tbl_time) {
  out.stats <- list()
  # out.stats[["na_percent"]] <- list()
  for(col in names(in.tbl_time)) {
    out.stats[["na_percent"]][col] <- 100*sum(is.na(in.tbl_time[,col][[1]]))/length(in.tbl_time[,col][[1]])
  }
  return(out.stats)
}
tt_na_stats(bf.daily.tt)
$na_percent
        DATE_TIME        sum.precip           min.spd           max.spd          mean.spd         max.dir.x         max.dir.y         min.dir.x         min.dir.y        mean.dir.x 
       0.00000000        0.00000000        0.00000000        0.00000000        0.03650301        0.00000000        0.00000000        0.00000000        0.00000000        0.08517369 
       mean.dir.y          min.temp          max.temp         mean.temp days.since.precip 
       0.08517369        0.00000000        0.00000000        0.10950903       61.51974205 

There are only a couple of columns that have NA or NaN values.

NA values in mean.temp

It is pretty easy to handle NA values in TEMP. We can fill the positions with exptrapolated values because this will usually be close enough.

bf.daily.tt$mean.temp <- trunc(zoo::na.approx(bf.daily.tt$mean.temp))
tt_na_stats(bf.daily.tt)
$na_percent
        DATE_TIME        sum.precip           min.spd           max.spd          mean.spd         max.dir.x         max.dir.y         min.dir.x         min.dir.y        mean.dir.x 
       0.00000000        0.00000000        0.00000000        0.00000000        0.03650301        0.00000000        0.00000000        0.00000000        0.00000000        0.08517369 
       mean.dir.y          min.temp          max.temp         mean.temp days.since.precip 
       0.08517369        0.00000000        0.00000000        0.00000000       61.51974205 

NA values in mean.dir

NA values in mean.dir likely indicates there wasn’t enough data in that field to get an average. Therefore, the best course of action is to replace both values with 0 which will indicate the direction wasn’t moving in either the x direction or the y direction. Note that this could cause issues when trying to turn x and y values back into compass directions.

bf.daily.tt$mean.dir.x <- ifelse(is.na(bf.daily.tt$mean.dir.x),0,bf.daily.tt$mean.dir.x)
bf.daily.tt$mean.dir.y <- ifelse(is.na(bf.daily.tt$mean.dir.y),0,bf.daily.tt$mean.dir.y)
tt_na_stats(bf.daily.tt)
$na_percent
        DATE_TIME        sum.precip           min.spd           max.spd          mean.spd         max.dir.x         max.dir.y         min.dir.x         min.dir.y        mean.dir.x 
       0.00000000        0.00000000        0.00000000        0.00000000        0.03650301        0.00000000        0.00000000        0.00000000        0.00000000        0.00000000 
       mean.dir.y          min.temp          max.temp         mean.temp days.since.precip 
       0.00000000        0.00000000        0.00000000        0.00000000       61.51974205 

NA values in mean.spd

NA values in speed are a bit harder to decide what to do with. There is no way of knowing whether the issue was that the machine measuring the data was down, or whether there just wasn’t any speed. Also, wind is extremely variable from day to day. Therefore it is decided to replace values with 0 for the beginning analysis, even though this may not be the best idea.

bf.daily.tt$mean.spd <- ifelse(is.na(bf.daily.tt$mean.spd),0,bf.daily.tt$mean.spd)
tt_na_stats(bf.daily.tt)
$na_percent
        DATE_TIME        sum.precip           min.spd           max.spd          mean.spd         max.dir.x         max.dir.y         min.dir.x         min.dir.y        mean.dir.x 
          0.00000           0.00000           0.00000           0.00000           0.00000           0.00000           0.00000           0.00000           0.00000           0.00000 
       mean.dir.y          min.temp          max.temp         mean.temp days.since.precip 
          0.00000           0.00000           0.00000           0.00000          61.51974 

Inf values in any column

Some of the values are Inf because of errors in handling missing data. There are only a few instances of this, therefore they will all be converted to

for(col in names(select(bf.daily.tt,-DATE_TIME))) {
    bf.daily.tt[col] <- (1-is.infinite(bf.daily.tt[,col][[1]]))*bf.daily.tt[col]
}
rm(col)

Plot Updated Daily Data

Temperature

temp.daily.xts <- xts(select(filter_time(bf.daily.tt, '2012' ~ '2017'),max.temp,mean.temp,min.temp),order.by = filter_time(bf.daily.tt, '2012' ~ '2017')$DATE_TIME)
temp.daily.xts %>% dygraph(main="Daily Average Bowman Field Temperature (F)") %>%
  dyAxis('y', label = "Temperature (F)") %>%
  dyRangeSelector(dateWindow = c("2016-01-01","2017-12-31")) %>%
  dyUnzoom()

Wind Direction

dir.x.daily.xts <- xts(select(filter_time(bf.daily.tt, '2012' ~ '2017'),mean.dir.x),order.by = filter_time(bf.daily.tt, '2012' ~ '2017')$DATE_TIME)
dir.x.daily.xts %>% dygraph(main="Average Daily Bowman Field Wind Direction (-1 < x < 1)") %>%
  dyAxis('y', label = "Daily Wind Direction (Compass Degrees)") %>%
  dyRangeSelector(dateWindow = c("2016-01-01","2017-12-31")) %>%
  dyUnzoom()

dir.y.daily.xts <- xts(select(filter_time(bf.daily.tt, '2012' ~ '2017'),mean.dir.y),order.by = filter_time(bf.daily.tt, '2012' ~ '2017')$DATE_TIME)
dir.y.daily.xts %>% dygraph(main="Average Daily Bowman Field Wind Direction (-1 < y < 1)") %>%
  dyAxis('y', label = "Daily Wind Direction (Compass Degrees)") %>%
  dyRangeSelector(dateWindow = c("2016-01-01","2017-12-31")) %>%
  dyUnzoom()

Wind Speed

spd.daily.xts <- xts(select(filter_time(bf.daily.tt, '2012' ~ '2017'),mean.spd),order.by = filter_time(bf.daily.tt, '2012' ~ '2017')$DATE_TIME)
spd.daily.xts %>% dygraph(main="Average Daily Bowman Field Wind Speed (mph)") %>%
  dyAxis('y', label = "Wind speed (mph)") %>%
  dyRangeSelector(dateWindow = c("2016-01-01","2017-12-31")) %>%
  dyUnzoom()

Precipitation

precip.daily.xts <- xts(select(filter_time(bf.daily.tt, '2012' ~ '2017'),sum.precip),order.by = filter_time(bf.daily.tt, '2012' ~ '2017')$DATE_TIME)
precip.daily.xts %>% dygraph(main="Daily Bowman Field Precipitation (Inches)") %>%
  dyAxis('y', label = "Precipitation (In)") %>%
  dyRangeSelector(dateWindow = c("2016-01-01","2017-12-31")) %>%
  dyUnzoom()

Days Since Last Rain

days.since.precip.daily.xts <- xts(select(filter_time(bf.daily.tt, '2012' ~ '2017'),days.since.precip),order.by = filter_time(bf.daily.tt, '2012' ~ '2017')$DATE_TIME)
days.since.precip.daily.xts %>% dygraph(main="Bowman Field Days Since Rain") %>%
  dyAxis('y', label = "Days Since Rain") %>%
  dyRangeSelector(dateWindow = c("2016-01-01","2017-12-31")) %>%
  dyUnzoom()

Save data for future analysis

save(bf.daily.tt, file = "BowmanField_Weather_Daily.RData")
LS0tDQp0aXRsZTogIkJvd21hbiBGaWVsZCBXZWF0aGVyXzAxX1Zpc3VhbGl6aW5nIGFuZCBSZS1lbmNvZGluZyBEYXRhIg0Kb3V0cHV0Og0KICBwZGZfZG9jdW1lbnQ6DQogICAgdG9jOiB5ZXMNCiAgaHRtbF9ub3RlYm9vazoNCiAgICB0b2M6IHllcw0KICBodG1sX2RvY3VtZW50Og0KICAgIGRmX3ByaW50OiBwYWdlZA0KICAgIHRvYzogeWVzDQotLS0NCg0KIyMgTG91aXN2aWxsZSBXZWF0aGVyIFRpbWUgU2VyaWVzIEFuYWx5c2lzDQoNClRoaXMgaXMgdGhlIHNlY29uZHMgaW4gYSBzZXJpZXMgb2YgZmlsZXMgcmVnYXJkaW5nIHRoZSBhbmFseXNpcyBvZiBXZWF0aGVyIERhdGEgaW4gTG91aXN2aWxsZS4NCg0KDQpGb3IgdGhpcyBhbmFseXNpcyBJIHdpbGwgYmUgdXNpbmcgYSBmZXcgcGFja2FnZXMgd2hpY2ggeW91IHdpbGwgbmVlZCBsb2FkZWQgaW4gb3JkZXIgdG8gZm9sbG93IGFsb25nOg0KDQpgYGB7ciBlY2hvPVRSVUUsIHJlc3VsdHM9J2hpZGUnLCBtZXNzYWdlPUZBTFNFLCBlcnJvcj1GQUxTRX0NCiMgTGlzdCBvZiBwYWNrYWdlcyB0byBsb2FkOg0KcGFja2FnZXMgPC0gYygidGlkeXZlcnNlIiwgImx1YnJpZGF0ZSIsICJ0aWJibGV0aW1lIiwgInJsYW5nIiwgImR5Z3JhcGhzIiwgImZvcmVjYXN0IiwgInpvbyIsICJ4dHMiLCAic3RyaW5nciIpDQogIA0KIyBDaGVjayB0byBzZWUgd2hldGhlciBhbnkgcGFja2FnZXMgYXJlbid0IGluc3RhbGxlZCBvbiB0aGUgY29tcHV0ZXIgYW5kIGluc3RhbGwNCm5ld19wYWNrYWdlcyA8LSBwYWNrYWdlc1shKHBhY2thZ2VzICVpbiUgaW5zdGFsbGVkLnBhY2thZ2VzKClbLCJQYWNrYWdlIl0pXQ0KaWYobGVuZ3RoKG5ld19wYWNrYWdlcykpIGluc3RhbGwucGFja2FnZXMobmV3X3BhY2thZ2VzKQ0KICANCiMgTG9hZCBOZWNjZXNzYXJ5IFBhY2thZ2VzDQpzYXBwbHkocGFja2FnZXMsIHJlcXVpcmUsIGNoYXJhY3Rlci5vbmx5ID0gVFJVRSkNCnJtKG5ld19wYWNrYWdlcykNCmBgYA0KDQoNCiMjIERhdGEgSW1wb3J0DQoNCkRhdGEgd2FzIGltcG9ydGVkLCBjbGVhbmVkLCBhbmQgcmVndWxhcml6ZWQgaW4gV2VhdGhlcl8wMS4gSW4gb3JkZXIgdG8gaW1wb3J0IHRoZSBkYXRhIG5vdyB3ZSBqdXN0IG5lZWQgdG8gbG9hZCB0aGUgUkRhdGEgZmlsZS4NCg0KYGBge3J9DQpsb2FkKGZpbGUgPSAiQm93bWFuRmllbGRfV2VhdGhlci5SRGF0YSIpDQpgYGANCg0KDQojIyBWaXN1YWxpemUgRGF0YQ0KDQojIyMgUGxvdCBEYXRhIGFuZCBkaXNjdXNzDQoNCiMjIyMgVGVtcGVyYXR1cmUNCg0KYGBge3J9DQp0ZW1wLnh0cyA8LSB4dHMoc2VsZWN0KGZpbHRlcl90aW1lKGJmLnR0LCAnMjAxMicgfiAnMjAxNycpLFRFTVApLG9yZGVyLmJ5ID0gZmlsdGVyX3RpbWUoYmYudHQsICcyMDEyJyB+ICcyMDE3JykkREFURV9USU1FKQ0KDQp0ZW1wLnh0cyAlPiUgZHlncmFwaChtYWluPSJCb3dtYW4gRmllbGQgVGVtcGVyYXR1cmUgKEYpIikgJT4lDQogIGR5QXhpcygneScsIGxhYmVsID0gIlRlbXBlcmF0dXJlIChGKSIpICU+JQ0KICBkeVNlcmllcygiVEVNUCIsIGF4aXMgPSAneScpICU+JQ0KICBkeVJhbmdlU2VsZWN0b3IoZGF0ZVdpbmRvdyA9IGMoIjIwMTYtMDEtMDEiLCIyMDE3LTEyLTMxIikpICU+JQ0KICBkeVVuem9vbSgpDQpgYGANCg0KQSByZXZpZXcgb2YgdGhlIGdyYXBoIHNob3dzIGEgY291cGxlIG9mIHRoaW5ncyBvZiBpbnRlcmVzdC4gVGhlIGZpcnN0IGludGVyZXN0aW5nIHRoaW5nIGlzIHRoYXQgdGhlcmUgaXMgYSB3aWRlIHZhcmlhdGlvbiBpbiBkYWlseSB0ZW1wZXJhdHVyZXMsIGJ1dCBpdCBhcHBlYXJzIHRoYXQgdGhlcmUgbWF5IGJlIG1vcmUgdmFyaWF0aW9uIGJldHdlZW4gZGF5cyBkdXJpbmcgdGhlIHdpbnRlciB0aGFuIGR1cmluZyB0aGUgc3VtbWVyLiBJdCBtaWdodCBiZSBiZXR0ZXIgdG8gdXNlIGRhaWx5IE1heC9NaW4vTWVhbiBkYXRhIGZvciB0aGlzIGFuYWx5c2lzIHRoYW4gdG8gdXNlIGhvdXJseSBkYXRhLg0KDQpBY3Rpb25zIHRvIGJlIHRha2VuOg0KDQoxLiBBZ2dyZWdhdGUgdG8gZGFpbHkNCiAgaSkgQ29sdW1uIGZvciBlYWNoIG9mIE1heCwgTWluLCBNZWFuDQoNCiMjIyMgV2luZCBEaXJlY3Rpb24NCg0KYGBge3J9DQpkaXIueHRzIDwtIHh0cyhzZWxlY3QoZmlsdGVyX3RpbWUoYmYudHQsICcyMDEyJyB+ICcyMDE3JyksRElSKSxvcmRlci5ieSA9IGZpbHRlcl90aW1lKGJmLnR0LCAnMjAxMicgfiAnMjAxNycpJERBVEVfVElNRSkNCg0KZGlyLnh0cyAlPiUgZHlncmFwaChtYWluPSJCb3dtYW4gRmllbGQgV2luZCBEaXJlY3Rpb24gKENvbXBhc3MgRGVncmVlcykiKSAlPiUNCiAgZHlBeGlzKCd5JywgbGFiZWwgPSAiV2luZCBEaXJlY3Rpb24gKENvbXBhc3MgRGVncmVlcykiKSAlPiUNCiAgZHlTZXJpZXMoIkRJUiIsIGF4aXMgPSAneScpICU+JQ0KICBkeVJhbmdlU2VsZWN0b3IoZGF0ZVdpbmRvdyA9IGMoIjIwMTYtMDEtMDEiLCIyMDE3LTEyLTMxIikpICU+JQ0KICBkeVVuem9vbSgpDQpgYGANCg0KRnJvbSB0aGUNCg0KIyMjIyBXaW5kIFNwZWVkDQoNCmBgYHtyfQ0Kc3BkLnh0cyA8LSB4dHMoc2VsZWN0KGZpbHRlcl90aW1lKGJmLnR0LCAnMjAxMicgfiAnMjAxNycpLFNQRCksb3JkZXIuYnkgPSBmaWx0ZXJfdGltZShiZi50dCwgJzIwMTInIH4gJzIwMTcnKSREQVRFX1RJTUUpDQoNCnNwZC54dHMgJT4lIGR5Z3JhcGgobWFpbj0iQm93bWFuIEZpZWxkIFdpbmQgU3BlZWQgKG1waCkiKSAlPiUNCiAgZHlBeGlzKCd5JywgbGFiZWwgPSAiV2luZCBzcGVlZCAobXBoKSIpICU+JQ0KICBkeVNlcmllcygiU1BEIiwgYXhpcyA9ICd5JykgJT4lDQogIGR5UmFuZ2VTZWxlY3RvcihkYXRlV2luZG93ID0gYygiMjAxNi0wMS0wMSIsIjIwMTctMTItMzEiKSkgJT4lDQogIGR5VW56b29tKCkNCmBgYA0KDQpXaW5kIHNwZWVkIGFwcGVhcnMgdG8gYmUgc2Vhc29uYWwsIHdpdGggbWF4aW11bSB3aW5kIHNwZWVkIGV4cGVjdGVkIGluIHRoZSBTcHJpbmcuIA0KDQpTb21lIGFjdGlvbnMgdG8gdGFrZToNCg0KMSkgQ29udmVydCBkYXRhIHRvIGRhaWx5DQogIGkpIENvbHVtbiBmb3IgTWF4LCBNaW4sIGFuZCBNZWFuIHdpbmQgc3BlZWQgcGVyIGRheQ0KDQojIyMjIFByZWNpcGl0YXRpb24NCg0KYGBge3J9DQpwcmVjaXAueHRzIDwtIHh0cyhzZWxlY3QoZmlsdGVyX3RpbWUoYmYudHQsICcyMDEyJyB+ICcyMDE3JyksUENQMDEpLG9yZGVyLmJ5ID0gZmlsdGVyX3RpbWUoYmYudHQsICcyMDEyJyB+ICcyMDE3JykkREFURV9USU1FKQ0KDQpwcmVjaXAueHRzICU+JSBkeWdyYXBoKG1haW49IkJvd21hbiBGaWVsZCBQcmVjaXBpdGF0aW9uIChJbmNoZXMpIikgJT4lDQogIGR5QXhpcygneScsIGxhYmVsID0gIlByZWNpcGl0YXRpb24gKEluKSIpICU+JQ0KICBkeVNlcmllcygiUENQMDEiLCBheGlzID0gJ3knKSAlPiUNCiAgZHlSYW5nZVNlbGVjdG9yKGRhdGVXaW5kb3cgPSBjKCIyMDE2LTAxLTAxIiwiMjAxNy0xMi0zMSIpKSAlPiUNCiAgZHlVbnpvb20oKQ0KYGBgDQoNClRoZXJlIGFyZSBhIGNvdXBsZSBvZiBpbnRlcmVzdGluZyB0aGluZ3MgdG8gdGhpbmsgYWJvdXQgb24gdGhpcyBncmFwaC4gRmlyc3QsIHRoZXJlIGFwcGVhcnMgdG8gYmUgc29tZSBzb3J0IG9mIHNlYXNvbmFsaXR5IGluIHRoZSBhbXBsaXR1ZGUgb2YgdGhlIHByZWNpcGlhdGlvbiBhbW91bnRzLiBNb3JlIHJhaW4gcGVyIGhvdXIgaXMgZXhwZWN0ZWQgZHVyaW5nIHRoZSBzdW1tZXIgd2hlbiBpdCByYWlucy4gU2Vjb25kLCB0aGlzIGdyYXBoIGlzIG9ubHkgc2hvd2luZyBhbXBsaXR1ZGUgb2YgdGhlIGRhdGEsIGJ1dCBpcyBub3QgY2xlYXIgaG93IG9mdGVuIGl0IHJhaW5zLiBUaW1lIHNpbmNlIGxhc3QgcmFpbiBtYXkgYmUgYXMgaW1wb3J0YW50IHRvIGFpciBxdWFsaXR5IGFzIHRoZSBhbW91bnQgb2YgcmFpbiBpcy4gDQoNCkFjdGlvbnMgdG8gdGFrZToNCg0KMSkgQWdncmVnYXRlIGRhdGEgdG8gZGFpbHkgYnkgdGFraW5nIFN1bSBvZiByYWluIGluIHRoYXQgZGF5LiANCjIpIENyZWF0ZSBhIG5ldyBjb2x1bW4gZm9yIGRheXMgc2luY2UgbGFzdCByYWluIChhbHNvIGRhaWx5KS4gDQoNCiMjIEFnZ3JlZ2F0ZSBkYXRhIHRvIGRhaWx5DQoNCk5leHQgc3RlcCBpcyB0byBhZ2dyZWdhdGUgYWxsIG9mIHRoZSBkYXRhIGludG8gZGFpbHkgdmFsdWVzLg0KDQpTdGFydCB3aXRoIGEgZnVuY3Rpb24gdG8gbWFrZSBpdCBjbGVhcmVyIGFuZCBlYXNpZXIuDQoNCmBgYHtyIGVjaG89VFJVRSwgcmVzdWx0cz0naGlkZScsIG1lc3NhZ2U9RkFMU0UsIGVycm9yPUZBTFNFfQ0KdHRfcGVyaW9kX2FwcGx5IDwtIGZ1bmN0aW9uKGluLnRibF90aW1lLCBpbi5wZXJpb2QsIGluLmZ1bmMgPSBtZWFuLCBuYV9ybSA9IFRSVUUpIHsNCiAgIyBQcm9ncmFtYXRpY2FsbHkgZmluZCB0aGUgaW5kZXggY29sdW1uDQogIGluZGV4X2NvbHVtbiA8LSBhdHRyaWJ1dGVzKGluLnRibF90aW1lKSRpbmRleF9xdW9bWzJdXQ0KICAjIENvbGxhcHNlIGJ5IHBlcmlvZCwgZ3JvdXAgYnkgaW5kZXgsIHRoZW4gc3VtbWFyaXNlIGJ5IGZ1bmN0aW9uLg0KICBvdXQudGJsX3RpbWUgPC0gaW4udGJsX3RpbWUgJT4lDQogICAgY29sbGFwc2VfYnkoaW4ucGVyaW9kKSAlPiUNCiAgICBncm91cF9ieSghISEgc3ltKGluZGV4X2NvbHVtbikpICU+JQ0KICAgIHN1bW1hcmlzZV9pZihpcy5udW1lcmljLCBpbi5mdW5jLCBuYS5ybSA9IG5hX3JtKQ0KfQ0KYGBgDQoNCiMjIyBBZ2dyZWdhdGUgVGVtcGVyYXR1cmUNCg0KYGBge3IgZWNobz1UUlVFLCByZXN1bHRzPSdoaWRlJywgbWVzc2FnZT1GQUxTRSwgZXJyb3I9RkFMU0V9DQp0ZW1wb3JhcnkudHQgPC0gYmYudHQgJT4lIHNlbGVjdChEQVRFX1RJTUUsVEVNUCkNCmJmLmRhaWx5LnR0IDwtIHR0X3BlcmlvZF9hcHBseSh0ZW1wb3JhcnkudHQsICdkYWlseScsIGluLmZ1bmMgPSBtZWFuLCBuYV9ybSA9IFRSVUUpICU+JQ0KICBzZWxlY3QoREFURV9USU1FLCBtZWFuLnRlbXAgPSBURU1QKQ0KYmYuZGFpbHkudHQgPC0gdHRfcGVyaW9kX2FwcGx5KHRlbXBvcmFyeS50dCwgJ2RhaWx5JywgaW4uZnVuYyA9IG1heCwgbmFfcm0gPSBUUlVFKSAlPiUNCiAgc2VsZWN0KERBVEVfVElNRSwgbWF4LnRlbXAgPSBURU1QKSAlPiUNCiAgZnVsbF9qb2luKGJmLmRhaWx5LnR0LGJ5ID0gJ0RBVEVfVElNRScpDQpiZi5kYWlseS50dCA8LSB0dF9wZXJpb2RfYXBwbHkodGVtcG9yYXJ5LnR0LCAnZGFpbHknLCBpbi5mdW5jID0gbWluLCBuYV9ybSA9IFRSVUUpICU+JQ0KICBzZWxlY3QoREFURV9USU1FLCBtaW4udGVtcCA9IFRFTVApICU+JQ0KICBmdWxsX2pvaW4oYmYuZGFpbHkudHQsYnkgPSAnREFURV9USU1FJykNCnJtKHRlbXBvcmFyeS50dCkNCmBgYA0KDQojIyMgQ29udmVydCBXaW5kIERpcmVjdGlvbiB0byBOL1MgYW5kIEUvVyB2YWx1ZXMNCg0KYGBge3IgZWNobz1UUlVFLCByZXN1bHRzPSdoaWRlJywgbWVzc2FnZT1GQUxTRSwgZXJyb3I9RkFMU0V9DQp0ZW1wb3JhcnkudHQgPC0gYmYudHQgJT4lIHNlbGVjdChEQVRFX1RJTUUsIERJUikNCiMgQ29udmVydCBkaXJlY3Rpb25zIGluIGRlZ3JlZXMgdG8gUmFkaWFucywgdGhlbiB0byB4IGFuZCB5DQp0ZW1wb3JhcnkudHQgPC0gdGVtcG9yYXJ5LnR0ICU+JQ0KICBtdXRhdGUoZGlyLnggPSByb3VuZChzaW4oRElSKnBpLzE4MCksZGlnaXRzID0gNiksDQogICAgICAgICBkaXIueSA9IHJvdW5kKGNvcyhESVIqcGkvMTgwKSxkaWdpdHMgPSA2KSkNCg0KYGBgDQoNCiMjIyBBZ2dyZWdhdGUgV2luZCBEaXJlY3Rpb24gWCBhbmQgWQ0KDQpgYGB7ciBlY2hvPVRSVUUsIHJlc3VsdHM9J2hpZGUnLCBtZXNzYWdlPUZBTFNFLCBlcnJvcj1GQUxTRX0NCnRlbXBvcmFyeS50dCA8LSB0ZW1wb3JhcnkudHQgJT4lIHNlbGVjdChEQVRFX1RJTUUsZGlyLngsIGRpci55KQ0KYmYuZGFpbHkudHQgPC0gdHRfcGVyaW9kX2FwcGx5KHRlbXBvcmFyeS50dCwgJ2RhaWx5JywgaW4uZnVuYyA9IG1lYW4sIG5hX3JtID0gVFJVRSkgJT4lDQogIHNlbGVjdChEQVRFX1RJTUUsIG1lYW4uZGlyLnggPSBkaXIueCwgbWVhbi5kaXIueSA9IGRpci55KSAlPiUNCiAgZnVsbF9qb2luKGJmLmRhaWx5LnR0LCBieSA9ICdEQVRFX1RJTUUnKQ0KYmYuZGFpbHkudHQgPC0gdHRfcGVyaW9kX2FwcGx5KHRlbXBvcmFyeS50dCwgJ2RhaWx5JywgaW4uZnVuYyA9IG1pbiwgbmFfcm0gPSBUUlVFKSAlPiUNCiAgc2VsZWN0KERBVEVfVElNRSwgbWluLmRpci54ID0gZGlyLngsIG1pbi5kaXIueSA9IGRpci55KSAlPiUNCiAgZnVsbF9qb2luKGJmLmRhaWx5LnR0LCBieSA9ICdEQVRFX1RJTUUnKQ0KYmYuZGFpbHkudHQgPC0gdHRfcGVyaW9kX2FwcGx5KHRlbXBvcmFyeS50dCwgJ2RhaWx5JywgaW4uZnVuYyA9IG1heCwgbmFfcm0gPSBUUlVFKSAlPiUNCiAgc2VsZWN0KERBVEVfVElNRSwgbWF4LmRpci54ID0gZGlyLngsIG1heC5kaXIueSA9IGRpci55KSAlPiUNCiAgZnVsbF9qb2luKGJmLmRhaWx5LnR0LCBieSA9ICdEQVRFX1RJTUUnKQ0Kcm0odGVtcG9yYXJ5LnR0KQ0KYGBgDQoNCiMjIyBBZ2dyZWdhdGUgV2luZCBTcGVlZA0KDQpgYGB7ciBlY2hvPVRSVUUsIHJlc3VsdHM9J2hpZGUnLCBtZXNzYWdlPUZBTFNFLCBlcnJvcj1GQUxTRX0NCnRlbXBvcmFyeS50dCA8LSBiZi50dCAlPiUgc2VsZWN0KERBVEVfVElNRSxTUEQpDQpiZi5kYWlseS50dCA8LSB0dF9wZXJpb2RfYXBwbHkodGVtcG9yYXJ5LnR0LCAnZGFpbHknLCBpbi5mdW5jID0gbWVhbiwgbmFfcm0gPSBUUlVFKSAlPiUNCiAgc2VsZWN0KERBVEVfVElNRSwgbWVhbi5zcGQgPSBTUEQpICU+JQ0KICBmdWxsX2pvaW4oYmYuZGFpbHkudHQsYnkgPSAnREFURV9USU1FJykNCmJmLmRhaWx5LnR0IDwtIHR0X3BlcmlvZF9hcHBseSh0ZW1wb3JhcnkudHQsICdkYWlseScsIGluLmZ1bmMgPSBtYXgsIG5hX3JtID0gVFJVRSkgJT4lDQogIHNlbGVjdChEQVRFX1RJTUUsIG1heC5zcGQgPSBTUEQpICU+JQ0KICBmdWxsX2pvaW4oYmYuZGFpbHkudHQsYnkgPSAnREFURV9USU1FJykNCmJmLmRhaWx5LnR0IDwtIHR0X3BlcmlvZF9hcHBseSh0ZW1wb3JhcnkudHQsICdkYWlseScsIGluLmZ1bmMgPSBtaW4sIG5hX3JtID0gVFJVRSkgJT4lDQogIHNlbGVjdChEQVRFX1RJTUUsIG1pbi5zcGQgPSBTUEQpICU+JQ0KICBmdWxsX2pvaW4oYmYuZGFpbHkudHQsYnkgPSAnREFURV9USU1FJykNCnJtKHRlbXBvcmFyeS50dCkNCmBgYA0KDQojIyMgQWdncmVnYXRlIFByZWNpcGl0YXRpb24NCg0KYGBge3IgZWNobz1UUlVFLCByZXN1bHRzPSdoaWRlJywgbWVzc2FnZT1GQUxTRSwgZXJyb3I9RkFMU0V9DQp0ZW1wb3JhcnkudHQgPC0gYmYudHQgJT4lIHNlbGVjdChEQVRFX1RJTUUsIFBDUDAxKQ0KYmYuZGFpbHkudHQgPC0gdHRfcGVyaW9kX2FwcGx5KHRlbXBvcmFyeS50dCwgJ2RhaWx5JywgaW4uZnVuYyA9IHN1bSwgbmFfcm0gPSBUUlVFKSAlPiUNCiAgc2VsZWN0KERBVEVfVElNRSwgc3VtLnByZWNpcCA9IFBDUDAxKSAlPiUNCiAgZnVsbF9qb2luKGJmLmRhaWx5LnR0LGJ5ID0gJ0RBVEVfVElNRScpDQpybSh0ZW1wb3JhcnkudHQpDQpgYGANCg0KDQojIyMgQWRkIG5ldyBjb2x1bW4gZm9yIGRheXMgc2luY2UgbGFzdCBQcmVjaXBpdGF0aW9uDQoNCmBgYHtyfQ0KYmYuZGFpbHkudHQgPC0gYmYuZGFpbHkudHQgJT4lIA0KICBtdXRhdGUoZGF5cy5zaW5jZS5wcmVjaXAgPSBOQSkNCnJhaW5fZGF0YV9kZXRlY3RlZCA8LSBGQUxTRQ0KZm9yKHJvdyBpbiAxOmxlbmd0aChiZi5kYWlseS50dCRkYXlzLnNpbmNlLnByZWNpcCkpIHsNCiAgaWYocmFpbl9kYXRhX2RldGVjdGVkKSB7DQogICAgYmYuZGFpbHkudHQkZGF5cy5zaW5jZS5wcmVjaXBbcm93XSA8LSBpZmVsc2UoYmYuZGFpbHkudHQkc3VtLnByZWNpcFtyb3ddIDw9IDAuMDEsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmZWxzZShpcy5uYShiZi5kYWlseS50dCRkYXlzLnNpbmNlLnByZWNpcFtyb3ctMV0pLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAxLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBiZi5kYWlseS50dCRkYXlzLnNpbmNlLnByZWNpcFtyb3ctMV0gKyAxKSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgMCkNCiAgfSBlbHNlIHsNCiAgICBpZihiZi5kYWlseS50dCRzdW0ucHJlY2lwW3Jvd10gPj0gMC4wMSkgew0KICAgICAgcmFpbl9kYXRhX2RldGVjdGVkIDwtIFRSVUUNCiAgICAgIGJmLmRhaWx5LnR0JGRheXMuc2luY2UucHJlY2lwW3Jvd10gPC0gMA0KICAgIH0NCiAgfQ0KICANCn0NCnJtKHJhaW5fZGF0YV9kZXRlY3RlZCxyb3cpDQpgYGANCg0KIyMgRGV0ZXJtaW5lIHdoYXQgdG8gZG8gYWJvdXQgTkEgdmFsdWVzLg0KDQpGb3IgbW9zdCBvZiB0aGUgdGltZSBzZXJpZXMgYW5hbHlzaXMgdGhlIHByb2dyYW0gbmVlZHMgdG8ga25vdyBob3cgdG8gaGFuZGxlIE5BIHZhbHVlcy4gTm90IGFuIGVhc3kgdGhpbmcgdG8gZG8gaW4gc29tZSBjYXNlcy4gT21pdHRpbmcgTkEgdmFsdWVzIGNyZWF0ZXMgYSBwcm9ibGVtIGJlY2F1c2UgdGhlIGZ1bmN0aW9ucyBhc3N1bWUgdGhhdCBhIHNlYXNvbmFsIHllYXIgaXMgMzY1IGRheXMgKGludGVnZXIgY2xvc2VzdCB0byAzNjUuMjUpLCBidXQgd2l0aCBOQSB2YWx1ZXMgbWlzc2luZyB0aGF0IHdpbGwgYmUgd3JvbmcuDQoNCmBgYHtyfQ0KdHRfbmFfc3RhdHMgPC0gZnVuY3Rpb24oaW4udGJsX3RpbWUpIHsNCiAgb3V0LnN0YXRzIDwtIGxpc3QoKQ0KICAjIG91dC5zdGF0c1tbIm5hX3BlcmNlbnQiXV0gPC0gbGlzdCgpDQogIGZvcihjb2wgaW4gbmFtZXMoaW4udGJsX3RpbWUpKSB7DQogICAgb3V0LnN0YXRzW1sibmFfcGVyY2VudCJdXVtjb2xdIDwtIDEwMCpzdW0oaXMubmEoaW4udGJsX3RpbWVbLGNvbF1bWzFdXSkpL2xlbmd0aChpbi50YmxfdGltZVssY29sXVtbMV1dKQ0KICB9DQogIHJldHVybihvdXQuc3RhdHMpDQp9DQoNCnR0X25hX3N0YXRzKGJmLmRhaWx5LnR0KQ0KYGBgDQoNClRoZXJlIGFyZSBvbmx5IGEgY291cGxlIG9mIGNvbHVtbnMgdGhhdCBoYXZlIE5BIG9yIE5hTiB2YWx1ZXMuIA0KDQoqIG1lYW4uc3BkDQoqIG1lYW4uZGlyLngNCiogbWVhbi5kaXIueQ0KKiBtZWFuLnRlbXANCg0KIyMjIE5BIHZhbHVlcyBpbiBtZWFuLnRlbXANCg0KSXQgaXMgcHJldHR5IGVhc3kgdG8gaGFuZGxlIE5BIHZhbHVlcyBpbiBURU1QLiBXZSBjYW4gZmlsbCB0aGUgcG9zaXRpb25zIHdpdGggZXhwdHJhcG9sYXRlZCB2YWx1ZXMgYmVjYXVzZSB0aGlzIHdpbGwgdXN1YWxseSBiZSBjbG9zZSBlbm91Z2guDQoNCmBgYHtyfQ0KYmYuZGFpbHkudHQkbWVhbi50ZW1wIDwtIHRydW5jKHpvbzo6bmEuYXBwcm94KGJmLmRhaWx5LnR0JG1lYW4udGVtcCkpDQp0dF9uYV9zdGF0cyhiZi5kYWlseS50dCkNCmBgYA0KDQojIyMgTkEgdmFsdWVzIGluIG1lYW4uZGlyDQoNCk5BIHZhbHVlcyBpbiBtZWFuLmRpciBsaWtlbHkgaW5kaWNhdGVzIHRoZXJlIHdhc24ndCBlbm91Z2ggZGF0YSBpbiB0aGF0IGZpZWxkIHRvIGdldCBhbiBhdmVyYWdlLiBUaGVyZWZvcmUsIHRoZSBiZXN0IGNvdXJzZSBvZiBhY3Rpb24gaXMgdG8gcmVwbGFjZSBib3RoIHZhbHVlcyB3aXRoIDAgd2hpY2ggd2lsbCBpbmRpY2F0ZSB0aGUgZGlyZWN0aW9uIHdhc24ndCBtb3ZpbmcgaW4gZWl0aGVyIHRoZSB4IGRpcmVjdGlvbiBvciB0aGUgeSBkaXJlY3Rpb24uIE5vdGUgdGhhdCB0aGlzIGNvdWxkIGNhdXNlIGlzc3VlcyB3aGVuIHRyeWluZyB0byB0dXJuIHggYW5kIHkgdmFsdWVzIGJhY2sgaW50byBjb21wYXNzIGRpcmVjdGlvbnMuDQoNCmBgYHtyfQ0KYmYuZGFpbHkudHQkbWVhbi5kaXIueCA8LSBpZmVsc2UoaXMubmEoYmYuZGFpbHkudHQkbWVhbi5kaXIueCksMCxiZi5kYWlseS50dCRtZWFuLmRpci54KQ0KYmYuZGFpbHkudHQkbWVhbi5kaXIueSA8LSBpZmVsc2UoaXMubmEoYmYuZGFpbHkudHQkbWVhbi5kaXIueSksMCxiZi5kYWlseS50dCRtZWFuLmRpci55KQ0KdHRfbmFfc3RhdHMoYmYuZGFpbHkudHQpDQpgYGANCg0KIyMjIE5BIHZhbHVlcyBpbiBtZWFuLnNwZA0KDQpOQSB2YWx1ZXMgaW4gc3BlZWQgYXJlIGEgYml0IGhhcmRlciB0byBkZWNpZGUgd2hhdCB0byBkbyB3aXRoLiBUaGVyZSBpcyBubyB3YXkgb2Yga25vd2luZyB3aGV0aGVyIHRoZSBpc3N1ZSB3YXMgdGhhdCB0aGUgbWFjaGluZSBtZWFzdXJpbmcgdGhlIGRhdGEgd2FzIGRvd24sIG9yIHdoZXRoZXIgdGhlcmUganVzdCB3YXNuJ3QgYW55IHNwZWVkLiBBbHNvLCB3aW5kIGlzIGV4dHJlbWVseSB2YXJpYWJsZSBmcm9tIGRheSB0byBkYXkuIFRoZXJlZm9yZSBpdCBpcyBkZWNpZGVkIHRvIHJlcGxhY2UgdmFsdWVzIHdpdGggMCBmb3IgdGhlIGJlZ2lubmluZyBhbmFseXNpcywgZXZlbiB0aG91Z2ggdGhpcyBtYXkgbm90IGJlIHRoZSBiZXN0IGlkZWEuDQoNCmBgYHtyfQ0KYmYuZGFpbHkudHQkbWVhbi5zcGQgPC0gaWZlbHNlKGlzLm5hKGJmLmRhaWx5LnR0JG1lYW4uc3BkKSwwLGJmLmRhaWx5LnR0JG1lYW4uc3BkKQ0KdHRfbmFfc3RhdHMoYmYuZGFpbHkudHQpDQpgYGANCg0KIyMjIEluZiB2YWx1ZXMgaW4gYW55IGNvbHVtbg0KDQpTb21lIG9mIHRoZSB2YWx1ZXMgYXJlIEluZiBiZWNhdXNlIG9mIGVycm9ycyBpbiBoYW5kbGluZyBtaXNzaW5nIGRhdGEuIFRoZXJlIGFyZSBvbmx5IGEgZmV3IGluc3RhbmNlcyBvZiB0aGlzLCB0aGVyZWZvcmUgdGhleSB3aWxsIGFsbCBiZSBjb252ZXJ0ZWQgdG8gDQoNCmBgYHtyfQ0KZm9yKGNvbCBpbiBuYW1lcyhzZWxlY3QoYmYuZGFpbHkudHQsLURBVEVfVElNRSkpKSB7DQogICAgYmYuZGFpbHkudHRbY29sXSA8LSAoMS1pcy5pbmZpbml0ZShiZi5kYWlseS50dFssY29sXVtbMV1dKSkqYmYuZGFpbHkudHRbY29sXQ0KfQ0Kcm0oY29sKQ0KYGBgDQoNCg0KIyMjIFBsb3QgVXBkYXRlZCBEYWlseSBEYXRhDQoNCiMjIyMgVGVtcGVyYXR1cmUNCg0KYGBge3J9DQp0ZW1wLmRhaWx5Lnh0cyA8LSB4dHMoc2VsZWN0KGZpbHRlcl90aW1lKGJmLmRhaWx5LnR0LCAnMjAxMicgfiAnMjAxNycpLG1heC50ZW1wLG1lYW4udGVtcCxtaW4udGVtcCksb3JkZXIuYnkgPSBmaWx0ZXJfdGltZShiZi5kYWlseS50dCwgJzIwMTInIH4gJzIwMTcnKSREQVRFX1RJTUUpDQoNCnRlbXAuZGFpbHkueHRzICU+JSBkeWdyYXBoKG1haW49IkRhaWx5IEF2ZXJhZ2UgQm93bWFuIEZpZWxkIFRlbXBlcmF0dXJlIChGKSIpICU+JQ0KICBkeUF4aXMoJ3knLCBsYWJlbCA9ICJUZW1wZXJhdHVyZSAoRikiKSAlPiUNCiAgZHlSYW5nZVNlbGVjdG9yKGRhdGVXaW5kb3cgPSBjKCIyMDE2LTAxLTAxIiwiMjAxNy0xMi0zMSIpKSAlPiUNCiAgZHlVbnpvb20oKQ0KYGBgDQoNCg0KIyMjIyBXaW5kIERpcmVjdGlvbg0KDQpgYGB7cn0NCmRpci54LmRhaWx5Lnh0cyA8LSB4dHMoc2VsZWN0KGZpbHRlcl90aW1lKGJmLmRhaWx5LnR0LCAnMjAxMicgfiAnMjAxNycpLG1lYW4uZGlyLngpLG9yZGVyLmJ5ID0gZmlsdGVyX3RpbWUoYmYuZGFpbHkudHQsICcyMDEyJyB+ICcyMDE3JykkREFURV9USU1FKQ0KDQpkaXIueC5kYWlseS54dHMgJT4lIGR5Z3JhcGgobWFpbj0iQXZlcmFnZSBEYWlseSBCb3dtYW4gRmllbGQgV2luZCBEaXJlY3Rpb24gKC0xIDwgeCA8IDEpIikgJT4lDQogIGR5QXhpcygneScsIGxhYmVsID0gIkRhaWx5IFdpbmQgRGlyZWN0aW9uIChDb21wYXNzIERlZ3JlZXMpIikgJT4lDQogIGR5UmFuZ2VTZWxlY3RvcihkYXRlV2luZG93ID0gYygiMjAxNi0wMS0wMSIsIjIwMTctMTItMzEiKSkgJT4lDQogIGR5VW56b29tKCkNCg0KZGlyLnkuZGFpbHkueHRzIDwtIHh0cyhzZWxlY3QoZmlsdGVyX3RpbWUoYmYuZGFpbHkudHQsICcyMDEyJyB+ICcyMDE3JyksbWVhbi5kaXIueSksb3JkZXIuYnkgPSBmaWx0ZXJfdGltZShiZi5kYWlseS50dCwgJzIwMTInIH4gJzIwMTcnKSREQVRFX1RJTUUpDQoNCmRpci55LmRhaWx5Lnh0cyAlPiUgZHlncmFwaChtYWluPSJBdmVyYWdlIERhaWx5IEJvd21hbiBGaWVsZCBXaW5kIERpcmVjdGlvbiAoLTEgPCB5IDwgMSkiKSAlPiUNCiAgZHlBeGlzKCd5JywgbGFiZWwgPSAiRGFpbHkgV2luZCBEaXJlY3Rpb24gKENvbXBhc3MgRGVncmVlcykiKSAlPiUNCiAgZHlSYW5nZVNlbGVjdG9yKGRhdGVXaW5kb3cgPSBjKCIyMDE2LTAxLTAxIiwiMjAxNy0xMi0zMSIpKSAlPiUNCiAgZHlVbnpvb20oKQ0KYGBgDQoNCg0KIyMjIyBXaW5kIFNwZWVkDQoNCmBgYHtyfQ0Kc3BkLmRhaWx5Lnh0cyA8LSB4dHMoc2VsZWN0KGZpbHRlcl90aW1lKGJmLmRhaWx5LnR0LCAnMjAxMicgfiAnMjAxNycpLG1lYW4uc3BkKSxvcmRlci5ieSA9IGZpbHRlcl90aW1lKGJmLmRhaWx5LnR0LCAnMjAxMicgfiAnMjAxNycpJERBVEVfVElNRSkNCg0Kc3BkLmRhaWx5Lnh0cyAlPiUgZHlncmFwaChtYWluPSJBdmVyYWdlIERhaWx5IEJvd21hbiBGaWVsZCBXaW5kIFNwZWVkIChtcGgpIikgJT4lDQogIGR5QXhpcygneScsIGxhYmVsID0gIldpbmQgc3BlZWQgKG1waCkiKSAlPiUNCiAgZHlSYW5nZVNlbGVjdG9yKGRhdGVXaW5kb3cgPSBjKCIyMDE2LTAxLTAxIiwiMjAxNy0xMi0zMSIpKSAlPiUNCiAgZHlVbnpvb20oKQ0KYGBgDQoNCg0KIyMjIyBQcmVjaXBpdGF0aW9uDQoNCmBgYHtyfQ0KcHJlY2lwLmRhaWx5Lnh0cyA8LSB4dHMoc2VsZWN0KGZpbHRlcl90aW1lKGJmLmRhaWx5LnR0LCAnMjAxMicgfiAnMjAxNycpLHN1bS5wcmVjaXApLG9yZGVyLmJ5ID0gZmlsdGVyX3RpbWUoYmYuZGFpbHkudHQsICcyMDEyJyB+ICcyMDE3JykkREFURV9USU1FKQ0KDQpwcmVjaXAuZGFpbHkueHRzICU+JSBkeWdyYXBoKG1haW49IkRhaWx5IEJvd21hbiBGaWVsZCBQcmVjaXBpdGF0aW9uIChJbmNoZXMpIikgJT4lDQogIGR5QXhpcygneScsIGxhYmVsID0gIlByZWNpcGl0YXRpb24gKEluKSIpICU+JQ0KICBkeVJhbmdlU2VsZWN0b3IoZGF0ZVdpbmRvdyA9IGMoIjIwMTYtMDEtMDEiLCIyMDE3LTEyLTMxIikpICU+JQ0KICBkeVVuem9vbSgpDQpgYGANCg0KIyMjIyBEYXlzIFNpbmNlIExhc3QgUmFpbg0KDQpgYGB7cn0NCmRheXMuc2luY2UucHJlY2lwLmRhaWx5Lnh0cyA8LSB4dHMoc2VsZWN0KGZpbHRlcl90aW1lKGJmLmRhaWx5LnR0LCAnMjAxMicgfiAnMjAxNycpLGRheXMuc2luY2UucHJlY2lwKSxvcmRlci5ieSA9IGZpbHRlcl90aW1lKGJmLmRhaWx5LnR0LCAnMjAxMicgfiAnMjAxNycpJERBVEVfVElNRSkNCg0KZGF5cy5zaW5jZS5wcmVjaXAuZGFpbHkueHRzICU+JSBkeWdyYXBoKG1haW49IkJvd21hbiBGaWVsZCBEYXlzIFNpbmNlIFJhaW4iKSAlPiUNCiAgZHlBeGlzKCd5JywgbGFiZWwgPSAiRGF5cyBTaW5jZSBSYWluIikgJT4lDQogIGR5UmFuZ2VTZWxlY3RvcihkYXRlV2luZG93ID0gYygiMjAxNi0wMS0wMSIsIjIwMTctMTItMzEiKSkgJT4lDQogIGR5VW56b29tKCkNCmBgYA0KDQojIyBTYXZlIGRhdGEgZm9yIGZ1dHVyZSBhbmFseXNpcw0KDQpgYGB7cn0NCnNhdmUoYmYuZGFpbHkudHQsIGZpbGUgPSAiQm93bWFuRmllbGRfV2VhdGhlcl9EYWlseS5SRGF0YSIpDQpgYGANCg0K